package bebetes;

import java.awt.Color;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Logger;

import fr.unice.plugin.ClassPluginLoader;
import fr.unice.plugin.Plugin;

/**
 * Fabrique des plugins
 */
public class FabriquePlugins
    extends FabriqueEntites {

  protected ClassPluginLoader pluginLoader;
  // Les plugins Bebete sont dans le repertoire plugins/repository
  // (chemin relatif au repertoire dans lequel java est lance).
  private static final String pluginDir = "plugins/repository";

  // liste des constructeurs valides pour chaque type de plugin supporte :
  // bebete et champi dans cette version
  protected Constructor[] bebeteConstructors;
  protected Constructor[] champiConstructors;
  protected Class[] comportementClass;
  protected Comportement[] comportementNormaux;
  protected Comportement[] comportementPredateurs;
  // Index des constructeurs "courant" pour chaque type de plugin supporte
  protected int bebeteIdx;
  protected int champiIdx;
  protected int comportementIdx;

  private static Logger logger =
      Logger.getLogger("fr.unice.plugin.PluginFactory");

  public FabriquePlugins() {
    try {
      pluginLoader = new ClassPluginLoader(pluginDir);
    }
    catch (MalformedURLException ex) {

    }
    loadPlugins();
  }

  /**
   * loadPlugins charge tous les plugins necessaires à l'usine
   * depuis le repertoire pluginDir
   * si un plugin a deja ete charge, il ne sera pas recharge
   */
  public void loadPlugins() {
	  pluginLoader.loadPlugins();
	  initConstructors();
	  initClass();
  }

  /**
   * reloadPlugins recharge tous les plugins necessaires à l'usine
   * depuis le repertoire pluginDir
   */
  public void reloadPlugins() {
    pluginLoader.reloadPlugins();
    initConstructors();
    initClass();
  }


  /**
   * getAllConstructors renvoie le tableau de tous les constructeurs des plugins
   * sous-classes de c et dont la signature est params
   *
   * @param c Class
   * @param params Class[]
   * @return Constructor[]
   */
  protected Constructor[] getAllConstructors(Class<? extends Plugin> c, Class... params) {
    ArrayList<Constructor> theConstructors = new ArrayList<Constructor>();
    List<Class<Plugin>> loadedOnes = pluginLoader.getPluginClasses(c);
    for (Class<Plugin> cp : loadedOnes) {
      try {
        Constructor cons = cp.getDeclaredConstructor(params);
        // l'instruction ci-dessous indique au Security Manager que l'on peut
        // acceder au constructeur, meme si celui-ci est protected ou private
        // sinon, on pourrait recevoir une IllegalAccessException lors de
        // l'instanciation avec ce constructeur
        cons.setAccessible(true);
        theConstructors.add(cons);
      }
      catch (SecurityException ex) {
        logger.warning("La classe " + cp.getName() +
                       " interdit la recuperation du constructeur");
      }
      catch (NoSuchMethodException ex) {
        logger.warning("La classe " + cp.getName() +
                       " n'a pas un constructeur a la bonne signature");
      }
    }
    return (Constructor[]) theConstructors.toArray(new Constructor[
        theConstructors.size()]);
  }
  
  /**
   * Recupere toutes les classes
   */
  protected Class[] getAllClass(Class<? extends Plugin> c)
  {
	  ArrayList<Class> theClass = new ArrayList<Class>();
	    List<Class<Plugin>> loadedOnes = pluginLoader.getPluginClasses(c);
    for (Class<Plugin> cl : loadedOnes) {
        try {
        	//logger.warning("Test de la classe " + cl.getName());
          Class classe = Class.forName(cl.getName());
          // l'instruction ci-dessous indique au Security Manager que l'on peut
          // acceder au constructeur, meme si celui-ci est protected ou private
          // sinon, on pourrait recevoir une IllegalAccessException lors de
          // l'instanciation avec ce constructeur
          theClass.add(classe);
        }
        catch (ClassNotFoundException e) {
        	logger.warning("La classe " + cl.getName() + " n'a pas été trouvée.");
    	}
      }
      return (Class[]) theClass.toArray(new Class[theClass.size()]);
  }

  /**
   * initConstructors initialise tous les constructeurs valides pour la factory
   */
  protected void initConstructors() {
    bebeteConstructors = getAllConstructors(BebeteAbstraite.class, ChampDeBebetes.class,
    			int.class, int.class, float.class, float.class, Color.class, Comportement.class, Comportement.class);
    // Aucun constructeur de beb�tes, impossible de continuer
    if (bebeteConstructors.length == 0) {
      logger.severe("Aucun constructeur de beb�tes trouve");
      System.exit(1);
    }
    // le premier constructeur du tableau est utilise par defaut
    setBebeteIdx(0);
    champiConstructors = getAllConstructors(Champi.class, new Class[] {
      ChampDeBebetes.class, int.class, int.class
    });
    // le chargement du constructeur de champi a echoue : impossible de continuer
    if (champiConstructors.length == 0) {
      logger.severe("Aucun constructeur de champi trouve");
      System.exit(1);
    }
    // le premier constructeur du tableau est utilise par defaut
    setChampiIdx(0);    
  }
  
  /**
   * Cherche les classes de types comportement
   */
  protected void initClass() {
	  ArrayList<Class> theClass = new ArrayList<Class>();
	  List<Class<Plugin>> loadedOnes = pluginLoader.getPluginClasses(Comportement.class);
	  comportementClass = (Class[]) loadedOnes.toArray(new Class[loadedOnes.size()]);
	    if (comportementClass.length == 0)
	    { 
	    	logger.severe("Aucune classe de comportement trouvee");
	    	System.exit(1);
	    }
	    setComportementIdx(0);
	    creeComportement();  
  }

  public Constructor[] getBebeteConstructors() {
    return bebeteConstructors;
  }

  public Constructor[] getChampiConstructors() {
    return champiConstructors;
  }
  
  public Class[] getComportementClass() {
	  return comportementClass;
  }

  public void setBebeteIdx(int idx) {
    bebeteIdx = idx;
  }

  public int getBebeteIdx() {
    return bebeteIdx;
  }

  public void setChampiIdx(int idx) {
    champiIdx = idx;
  }

  public int getChampiIdx() {
    return champiIdx;
  }
  
  public void setComportementIdx(int idx)
  {
	  comportementIdx = idx;
  }
  
  public int getComportementIdx()
  {
	  return comportementIdx;
  }

  /**
   * Creation d'une beb�te a partir du constructeur d'index courant bebeteIdx
   * @param c ChampDeBebetes
   * @param x int
   * @param y int
   * @param dC float
   * @param vC float
   * @param col Color
   * @return BebeteAbstraite
   */
  public BebeteAbstraite creeBebete(ChampDeBebetes c, int x, int y, float dC,
                                    float vC, Color col, Comportement compNormal, Comportement compPredateur) {
    Object instance = null;
    try {
      instance = bebeteConstructors[bebeteIdx].newInstance(c,x,y,dC,vC,col, compNormal, compPredateur);
    }
    catch (Exception ex) {
      logger.severe("Impossibilite d'instancier une beb�te de type" +
                    bebeteConstructors[bebeteIdx].getDeclaringClass().getName());
      return null;
    }
    return (BebeteAbstraite) instance;
  }

  /**
   * Creation d'un champ (pas de plugin utilise mais c'est tout à fait
   * faisable et souhaitable !!!)
   * @param largeur int
   * @param hauteur int
   * @param nb int
   * @return ChampDeBebetes
   */
  public ChampDeBebetes creeChampDeBebetes(int largeur, int hauteur, int nb) {
    // Le champ n'est pas charge comme un plugin, on pourrait le faire !
    return new ChampDeBebetesAunChampi(largeur, hauteur, nb);
  }

  /**
   * Creation d'un champi a partir du constructeur d'index courant champiIdx
   * @param c ChampDeBebetes
   * @param x int
   * @param y int
   * @return Champi
   */
  public Champi creeChampi(ChampDeBebetes c, int x, int y) {
    Object instance = null;
    try {
      instance = champiConstructors[champiIdx].newInstance(c,x,y);
    }
    catch (Exception ex) {
      logger.severe("Impossibilite d'instancier un champi de type" +
                    champiConstructors[champiIdx].getDeclaringClass().getName());
      return null;
    }
    return (Champi) instance;
  }

  /**
   * Recupere les differents type de comportement
   */
  private void creeComportement()
  {
  	Comportement comp;
  	String predateur = "ComportementPredateur";
  	ArrayList<Comportement> compPredateur = new ArrayList<Comportement>();
  	ArrayList<Comportement> compNormaux = new ArrayList<Comportement>();
  	for (int i=0; i < comportementClass.length; i++)
  	{
  		try
  		{
	  		Method m = comportementClass[i].getMethod("getComportement");
	  		comp = (Comportement) m.invoke(null);
		  if (comportementClass[i].getName().indexOf(predateur) != -1)
			  compPredateur.add(comp);
		  else
			  compNormaux.add(comp);
  		}
  		catch (Exception ex)
  		{
  			logger.severe("Impossibilite d'obetenir une instance de type" +
                    comportementClass[i].getName());
  		}
  	}
  	comportementNormaux = (Comportement[]) compNormaux.toArray(new Comportement[compNormaux.size()]);
  	comportementPredateurs = (Comportement[]) compPredateur.toArray(new Comportement[compPredateur.size()]);
  }

	@Override
	public Comportement[] getComportementNormaux() {
		return comportementNormaux;
	}
	
	@Override
	public Comportement[] getComportementPredateurs() {
		return comportementPredateurs;
	}
	  	

  
}